﻿using System;
using System.Security.Cryptography;
using System.Text;

namespace BrettM.Helpers
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks>
    /// Taken from the post A C# Password Generator on CodeProject by Kevin Stewart and slightly modifed

    /// </remarks>
    /// <see cref="http://www.codeproject.com/KB/cs/pwdgen.aspx" />
    public class PasswordGenerator
    {
        [Flags]
        public enum PasswordSet
        {
            LowerCharacters = 1 << 1,
            UpperCharacter = 1 << 2,
            Numbers = 1 << 3,
            Punctuation = 1 << 4,
            All = LowerCharacters + UpperCharacter + Numbers + Punctuation,
            LettersNumbers = LowerCharacters + UpperCharacter + Numbers,
            UpperLettersNumbers = UpperCharacter + Numbers,
            LowerLettersNumbers = LowerCharacters + Numbers
        }



        public PasswordGenerator()
            : this(PasswordSet.All)
        {
        }

        public PasswordGenerator(PasswordSet set)
            : this(GetPasswordString(set))
        {

        }

        private static char[] GetPasswordString(PasswordSet set)
        {
            string chars = string.Empty;

            if ((set & PasswordSet.LowerCharacters) == PasswordSet.LowerCharacters)
            {
                chars += PwdCharLower;
            }

            if ((set & PasswordSet.UpperCharacter) == PasswordSet.UpperCharacter)
            {
                chars += PwdCharUpper;
            }

            if ((set & PasswordSet.Punctuation) == PasswordSet.Punctuation)
            {
                chars += PwdCharPunctuation;
            }

            if ((set & PasswordSet.Numbers) == PasswordSet.Numbers)
            {
                chars += PwdCharNumber;
            }

            return chars.ToCharArray();
        }


        public PasswordGenerator(char[] charSet)
        {
            this._pwdCharArray = charSet;
            this.Minimum = DefaultMinimum;
            this.Maximum = DefaultMaximum;
            this.ConsecutiveCharacters = false;
            this.RepeatCharacters = true;
            this.ExcludeSymbols = false;
            this.Exclusions = null;

            rng = new RNGCryptoServiceProvider();
        }

        protected int GetCryptographicRandomNumber(int lBound, int uBound)
        {
            // Assumes lBound >= 0 && lBound < uBound
            // returns an int >= lBound and < uBound
            uint urndnum;
            byte[] rndnum = new Byte[4];
            if (lBound >= uBound)
            {
                // test for degenerate case where only lBound can be returned
                return lBound;
            }

            uint xcludeRndBase = (uint.MaxValue -
                (uint.MaxValue % (uint)(uBound - lBound)));

            do
            {
                rng.GetBytes(rndnum);
                urndnum = System.BitConverter.ToUInt32(rndnum, 0);
            } while (urndnum >= xcludeRndBase);

            return (int)(urndnum % (uBound - lBound)) + lBound;
        }

        protected char GetRandomCharacter()
        {
            var upperBound = _pwdCharArray.GetUpperBound(0);

            if (true == this.ExcludeSymbols)
            {
                upperBound = PasswordGenerator.UBoundDigit;
            }

            var randomCharPosition = GetCryptographicRandomNumber(
                _pwdCharArray.GetLowerBound(0), upperBound);

            var randomChar = _pwdCharArray[randomCharPosition];

            return randomChar;
        }

        public string Generate()
        {
            // Pick random length between minimum and maximum   
            var pwdLength = GetCryptographicRandomNumber(this.Minimum,
                this.Maximum);

            var pwdBuffer = new StringBuilder();
            pwdBuffer.Capacity = this.Maximum;

            // Generate random characters
            char nextCharacter;

            // Initial dummy character flag
            var lastCharacter = nextCharacter = '\n';

            for (var i = 0; i < pwdLength; i++)
            {
                nextCharacter = GetRandomCharacter();

                if (false == this.ConsecutiveCharacters)
                {
                    while (lastCharacter == nextCharacter)
                    {
                        nextCharacter = GetRandomCharacter();
                    }
                }

                if (false == this.RepeatCharacters)
                {
                    var temp = pwdBuffer.ToString();
                    var duplicateIndex = temp.IndexOf(nextCharacter);
                    while (-1 != duplicateIndex)
                    {
                        nextCharacter = GetRandomCharacter();
                        duplicateIndex = temp.IndexOf(nextCharacter);
                    }
                }

                if ((null != this.Exclusions))
                {
                    while (-1 != this.Exclusions.IndexOf(nextCharacter))
                    {
                        nextCharacter = GetRandomCharacter();
                    }
                }

                pwdBuffer.Append(nextCharacter);
                lastCharacter = nextCharacter;
            }

            return pwdBuffer.ToString();
        }

        public string Exclusions { get; set; }

        public int Minimum
        {
            get { return this._minSize; }
            set
            {
                this._minSize = value;
                if (PasswordGenerator.DefaultMinimum > this._minSize)
                {
                    this._minSize = PasswordGenerator.DefaultMinimum;
                }
            }
        }

        public int Maximum
        {
            get { return this._maxSize; }
            set
            {
                this._maxSize = value;
                if (this._minSize > this._maxSize)
                {
                    this._maxSize = PasswordGenerator.DefaultMaximum;
                }
            }
        }

        public bool ExcludeSymbols { get; set; }

        public bool RepeatCharacters { get; set; }

        public bool ConsecutiveCharacters { get; set; }

        private const int DefaultMinimum = 6;
        private const int DefaultMaximum = 10;
        private const int UBoundDigit = 61;

        private readonly RNGCryptoServiceProvider rng;
        private int _minSize;
        private int _maxSize;
        private readonly char[] _pwdCharArray;


        private const string PwdCharLower = "abcdefghijklmnopqrstuvwxyz";
        private const string PwdCharUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        private const string PwdCharNumber = "0123456789";
        private const string PwdCharPunctuation = "`~!@#$%^&*()-_=+[]{}\\|;:'\",<.>/?";
    }
}
